// Copyright 2006 Bret Taylor
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

#include "debug.h"
#include "type.h"

namespace indie {

Type::Type(const string& name) :
    name_(name) {
}


Type::~Type() {
}


bool Type::IsSubTypeOf(const Type& other) const {
    const ClassType* class_type = dynamic_cast<const ClassType*>(&other);
    if (class_type != NULL) return IsSubTypeOf(*class_type);
    const ArrayType* array_type = dynamic_cast<const ArrayType*>(&other);
    assert(array_type != NULL);
    return IsSubTypeOf(*array_type);
}


bool Type::Equals(const Type& other) const {
    return name() == other.name();
}


Method::Method(const UnionType& return_type) :
    return_type_(return_type) {
}


Method::~Method() {
}


ClassType::ClassType(const AstClass& ast, const ClassType* superclass) :
    Type(ast.identifier()), ast_(&ast), superclass_(superclass) {
    if (!has_superclass()) set_superclass(ClassType::Object());
}


ClassType::ClassType(const string& name, const ClassType* superclass) :
    Type(name), ast_(NULL), superclass_(superclass) {
}


ClassType::~ClassType() {
    for (vector<Method*>::iterator i = methods_.begin();
	 i != methods_.end(); ++i) {
	delete *i;
    }
}


bool ClassType::IsSubTypeOf(const ClassType& other) const {
    if (Equals(other)) return true;
    if (!has_superclass()) return false;
    return superclass().IsSubTypeOf(other);
}


bool ClassType::IsSubTypeOf(const ArrayType& other) const {
    return false;
}


const ClassType& ClassType::Object() {
    static ClassType type("Object");
    return type;
}

const ClassType& ClassType::Int() {
    static ClassType type("int", &ClassType::Object());
    return type;
}

const ClassType& ClassType::Float() {
    static ClassType type("float", &ClassType::Object());
    return type;
}

const ClassType& ClassType::Char() {
    static ClassType type("char", &ClassType::Object());
    return type;
}

const ClassType& ClassType::Boolean() {
    static ClassType type("boolean", &ClassType::Object());
    return type;
}

const ClassType& ClassType::String() {
    static ClassType type("String", &ClassType::Object());
    return type;
}



ArrayType::ArrayType(const Type& element_type) :
    Type(element_type.name() + "[]"), element_type_(element_type) {
}

ArrayType::~ArrayType() {
}


bool ArrayType::IsSubTypeOf(const ClassType& other) const {
    return false;
}

bool ArrayType::IsSubTypeOf(const ArrayType& other) const {
    if (Equals(other)) return true;
    return false;
}

UnionType::UnionType(vector<const Type*>* types) : types_(types) {
    assert(types != NULL);
}

UnionType::~UnionType() {
}

string UnionType::ToString() const {
    string name = "";
    for (vector<const Type*>::const_iterator i = types().begin();
	 i != types().end(); ++i) {
	if (i != types().begin()) name += " ";
	name += (*i)->name();
    }
    return name;
}

bool UnionType::Equals(const UnionType& other) const {
    if (types().size() != other.types().size()) return false;

    for (vector<const Type*>::const_iterator i = types().begin();
	 i != types().end(); ++i) {
	bool success = false;
	for (vector<const Type*>::const_iterator j = other.types().begin();
	     j != other.types().end(); ++j) {
	    if ((*i)->Equals(**j)) {
		success = true;
		break;
	    }
	}
	if (!success) return false;
    }
    return true;
}

bool UnionType::IsSubTypeOf(const UnionType& other) const {
    for (vector<const Type*>::const_iterator i = types().begin();
	 i != types().end(); ++i) {
	bool match = false;
	for (vector<const Type*>::const_iterator j = other.types().begin();
	     j != other.types().end(); ++j) {
	    if ((*i)->IsSubTypeOf(**j)) {
		match = true;
		break;
	    }
	}
	if (!match) return false;
    }
    return true;
}

bool UnionType::IsSubTypeOf(const Type& other) const {
    for (vector<const Type*>::const_iterator i = types().begin();
	 i != types().end(); ++i) {
	if (!(*i)->IsSubTypeOf(other)) return false;
    }
    return true;
}


class ErrorType : public UnionType {
  public:
    ErrorType() : UnionType(new vector<const Type*>) {
    }

    virtual ~ErrorType() {
    }

    virtual string ToString() const {
	return "<error>";
    }

    virtual bool IsSubTypeOf(const UnionType& other) const {
	return true;
    }

    virtual bool IsSubTypeOf(const Type& other) const {
	return true;
    }
};

const UnionType& UnionType::Error() {
    static ErrorType type;
    return type;
}


class EmptyType : public UnionType {
  public:
    EmptyType() : UnionType(new vector<const Type*>) {
    }

    virtual ~EmptyType() {
    }

    virtual string ToString() const {
	return "<empty>";
    }

    virtual bool IsSubTypeOf(const UnionType& other) const {
	return false;
    }

    virtual bool IsSubTypeOf(const Type& other) const {
	return false;
    }
};

const UnionType& UnionType::Empty() {
    static EmptyType type;
    return type;
}


const UnionType& UnionType::Int() {
    static UnionType type(new vector<const Type*>);
    if (type.types().size() == 0) {
	type.types_->push_back(&ClassType::Int());
    }
    return type;
}

const UnionType& UnionType::Float() {
    static UnionType type(new vector<const Type*>);
    if (type.types().size() == 0) {
	type.types_->push_back(&ClassType::Float());
    }
    return type;
}

const UnionType& UnionType::Char() {
    static UnionType type(new vector<const Type*>);
    if (type.types().size() == 0) {
	type.types_->push_back(&ClassType::Char());
    }
    return type;
}

const UnionType& UnionType::Boolean() {
    static UnionType type(new vector<const Type*>);
    if (type.types().size() == 0) {
	type.types_->push_back(&ClassType::Boolean());
    }
    return type;
}

const UnionType& UnionType::String() {
    static UnionType type(new vector<const Type*>);
    if (type.types().size() == 0) {
	type.types_->push_back(&ClassType::String());
    }
    return type;
}

}
